<?php
/**
 * ImpSoft Category Model
 * All functionality for the category goes in here.
 * 
 * @package ImpSoft
 * @subpackage ImpSoft Models
 * @copyright Luke Visinoni (luke.visinoni@gmail.com)
 * @author Luke Visinoni (luke.visinoni@gmail.com)
 * @license GNU Lesser General Public License
 */
class Category extends ImpSoft_Db_Table {

    protected $_name = 'categories';
    protected $_primary = 'category_id';
    protected $_dependentTables = array();
    protected $_rowClass = 'CategoryRow';
    protected $_required = array('name');
	protected $_tree = array();
	/**
	 * Add any filters that should be applied to this model when saving

	public function getFilters() {
	
		$filters = new Q_Filter_Set();
		return $filters;
	
	}
	 */
	/**
	 * Add any rules that should be applied to validation when saving
	 */
	public function getRules() {
	
		$rules = new Q_Rule_Set();
		$rules->add(new Q_Rule_NotNull(), array('name'), 'This is a required field');
		return $rules;
	
	}
	/**
	 * Gets a list of categories for an html select
	 */
	public function getListForSelect($select = null) {
	
		$return = array('' => " - Select One - ");
		if (!($select instanceof Zend_Db_Select)) {
			$select = $this->select()->where("is_active = ?", true);
		}
		$categories = $this->fetchAll($select);
		if ($categories->count() > 0) {
			foreach ($categories as $category) {
				$return[$category['category_id']] = $category['name'];
			}
		}
		return $return;
	
	}
	/**
	 * Build a tree of categories
	 * @todo This needs to use the modified pre-order tree traversal method instead of the
	 * 		 standard tree method its using now.
	 * @todo This also needs to be optimized. It's not written very well.
	 * @todo Implement caching of some kind
	 */
	protected function initializeTree() {
	
		$rows = $this->fetchAll($this->select()->where("is_active = ?", true)->order("name ASC"));
		$this->_tree = array();
		foreach ($rows as $row) {
			// not concerned with anything but root nodes
			if (!empty($row->parent_id)) {
				continue;
			}
			$row = $row->toArray();
			$row['children'] = $this->getChildren($row['category_id'], $rows->toArray());
			$this->_tree[] = $row;
		}
	
	}
	/**
	 * Gets the children of the passed-in category id from the passed-in categories
	 * @param $category_id The category ID that you want to get the children of
	 * @param $categories A list of categories from which to find the children of $category_id
	 */
	protected function getChildren($parent_id, $categories) {
	
		$children = array();
		foreach ($categories as $category) {
			// find the categories who's parent id is $parent_id
			if ($category['parent_id'] !== $parent_id) {
				continue;
			}
			// find their children as well
			$category['children'] = $this->getChildren($category['category_id'], $categories);
			$children[] = $category;
		}
		// return the list of children categories
		return $children;
	
	}
	/**
	 * Get the tree of categories
	 */
	public function getTree() {
	
		$this->initializeTree();
		return $this->_tree;
	
	}
	/**
	 * Get all of the categories for the category admin list screen
	 * @todo Figure out why the hell joinLeft won't work and add back the parentname column
	 * I just realized that the above problem may have something to do with the order column having
	 * a reserved name. Maybe I should change it to ord or something.
	 */
	public function getListForAdmin($order = null, $direction = null) {
	
		$order = ($order) ? $order : "cat.name";
		$direction = ($direction) ? $direction : "asc";
		$db = $this->getAdapter();
		$select = $db->select()
			->from(array('cat' => 'categories'), array('category_id', 'parent_id', 'name', 'description', 'is_active', 'order'))
			//->joinLeft(array('parent' => 'categories'), 'cat.parent_id = parent.category_id', array('parentname' => 'parent.name'))
			->order("$order $direction");
		return $select;
	
	}
	/**
	 * Get all products from a certain category
	 */
	public function getProducts($category_id) {
	
		$db = $this->getAdapter();
		$select = $db->select()
		   ->from(array("p" => "products"), array("product_id", "title", "description", "cost", "price", "weight", "sku", "upc", "modified", "created", "slug"))
		   ->join(array("cp" => "cpjoin"), "p.product_id = cp.product_id")
		   ->where("cpjoin.category_id = ?", $category_id)
		   ->order("p.title ASC");
		return $db->fetchAll($select);                                                                                                                                       
	
	}
	/**
	 * Update a category's order column and update all categories who's order
	 * is larger than that category's order by one.
	 * @todo I had to remove the unique constraint in categories.count for this to work. It would be nice if I could put it back
	 * @param Category|integer Either a Category object or an integer representing it's primary key
	 * @param integer The order that you are setting the category to
	 */
	public function updateOrder($category, $order) {
	
		$db = $this->getAdapter();
		$orderColumn = $db->quoteIdentifier("order");
		if ($category instanceof Category) {
			// do something
		} else {
			$catset = $this->find($category);
			if (!$catset->count()) {
				throw new ImpSoft_Exception_CategoryNotFound("Category order update failed. Category with primary key '$category' could not be found.");
			}
			$category = $catset->current();
		}
		$db->beginTransaction();
		try {
			// find out if anything needs to be updated. if there is nothing with the count value of $count, we don't need to do anything else
			$withsamecount = $this->fetchAll($this->select()->where("$orderColumn = ?", $order));
			if ($withsamecount->count()) {
				// if we are replacing a category's count, update all that are bigger than it by one
				$updateCategories = $this->fetchAll($this->select()->where("category_id <> ?", $category->category_id)->where("$orderColumn >= ?", $order)->order("order ASC"));
				foreach ($updateCategories as $cat) {
					$cat->order = $cat->order + 1;
					$cat->save();
				}
			}
			$category->order = $order;
			$category->save();
			$db->commit();
		} catch(Zend_Db_Exception $e) {
			$db->rollback();
		}
	
	}
	/**
	 * Fetch category info by slug
	 */
	public function fetchBySlug($slug) {
	
		$select = $this->select()->where('slug = ?', $slug);
		return $this->fetchRow($select);
	
	}
	/**
	 * Fetch products by category slug
	 */
	public function fetchProductsByCategorySlug($slug) {
	
		$db = $this->getAdapter();
		$select = $db->select()
		   ->from(array('p' => 'products'), array('product_id', 'title', 'description', 'cost', 'price', 'weight', 'sku', 'upc', 'modified', 'created', 'slug'))
		   ->join(array('cp' => 'categories_products'), 'p.product_id = cp.product_id', array())
		   ->join(array('c' => 'categories'), 'c.category_id = cp.category_id', array('name'))
		   ->where("c.slug = ?", $slug)
		   ->order("p.title ASC");
		return $select;
	
	}
	/**
	 * Fetch all nodes between root_id
	function display_tree($root_id) {
	
		$root = $this->find($root_id);
		
		// start with an empty $right stack
		$right = array();
		
		// now, retrieve all descendants of the $root node
		$results = $this->fetchAll($this->select()->where("lft BETWEEN ? AND ?", $row->lft, $row->rgt)->order("lft ASC"));
		
		// display each row
		foreach ($results as $result) {
		    // only check stack if there is one
		    if (count($right) > 0) {
		        // check if we should remove a node from the stack
		        while ($right[count($right)-1] < $result->rgt) {
		            array_pop($right);
		        }
		    }
			
			echo "<pre>";
		    // display indented node title
		    echo str_repeat('  ', count($right)) . $result->name . "\n";
			echo "</pre>";
			
		    // add this node to the stack
		    $right[] = $result->rgt;
		}
	
	}
		 */
	/**
	 * Rebuild Tree after making adjustments to parent/children relationships
	 * or adding/removing a node
	function rebuild_tree($parent, $left) {
	
		// the right value of this node is the left value + 1
		$right = $left + 1;
		
		// get all children of this node
		$results = $this->fetchAll($this->select()->where("parent_id = ?", $parent));

		while ($results as $row) {
		    // recursive execution of this function for each
		    // child of this node
		    // $right is the current right value, which is
		    // incremented by the rebuild_tree function
		    $right = $this->rebuild_tree($row->getPk(), $right);
		}

		// we've got the left value, and now that we've processed
		// the children of this node we also know the right value
		$row = $this->find($parent);
		$row->lft = $left;
		$row->rgt = $right;
		$row->save();

		// return the right value of this node + 1
		return $right + 1;
	
	}
		 */

}